//
//  GCDViewController.m
//  DebugDemo
//
//  Created by XFB on 2018/11/22.
//  Copyright © 2018年 ZhouCheng. All rights reserved.
//

#import "GCDViewController.h"
#import "HTTPClient.h"
#import "ArticleModel.h"

@interface GCDViewController ()

@end

@implementation GCDViewController

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    [HTTPClient cancelAllRequest];
}

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupBasicSet];
    [self setupUI];
}

- (void)setupBasicSet
{
    self.view.backgroundColor = [UIColor whiteColor];
    self.navigationItem.title = @"详解GCD";
}

- (void)setupUI
{
    /// 线程组 第一次进入可以，后面不行了
//    [self test1];

    /// 信号量
//    [self test2];
    
    /// 同步执行+主队列 造成死锁
//    [self test3];

    /// dispatch_barrier_sync
    [self test4];
}

- (void)test1
{
    /// 线程组
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

    /// 请求1
    /// 进入组
    dispatch_group_enter(group);
    dispatch_group_async(group, globalQueue, ^{
        [ArticleModel getArticleDataWithParameter:nil success:^(ResultInfo *resultInfo) {
            NSLog(@"1---网络请求接口完成");
            dispatch_group_leave(group);
        } failure:^(NSError *error) {
            NSLog(@"%@", error.localizedDescription);
            dispatch_group_leave(group);
        }];
    });
    
    /// 请求2
    /// 进入组
    dispatch_group_enter(group);
    dispatch_group_async(group, globalQueue, ^{
        [ArticleModel getArticleDataWithParameter:nil success:^(ResultInfo *resultInfo) {
            NSLog(@"2---网络请求接口完成");
            dispatch_group_leave(group);
        } failure:^(NSError *error) {
            NSLog(@"%@", error.localizedDescription);
            dispatch_group_leave(group);
        }];
    });
    
    /// 当组里面的任务都清空以后，通知主线程完成了所有任务
    dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
       /// 最终操作
        NSLog(@"3---网络请求全部完成");
    });
    
    /**
     XFB里面的网络请求只有一个回调，不论最终成功还是失败，只需要写一次 dispatch_group_leave
     如果你的回调block有两个，分成功的回调和错误的回调，那么你必须在两个block里都写 dispatch_group_leave
     因为你不知道你的请求到底是成功block还是失败block
     如果少写了，一旦某个请求失败了，那么你的notify 方法永远也不会执行了
     */
}

- (void)test2
{
    
    dispatch_queue_t queue = dispatch_queue_create("ZCMiracle", NULL);
    /// 获取队列组
    dispatch_group_t group = dispatch_group_create();
    /// 创建信号量 用于线程同步
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    for (NSInteger i = 0; i < 5; i ++) {
        NSLog(@"网络请求%ld开始", i);
        /// 进组
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            [ArticleModel getArticleDataWithParameter:nil success:^(ResultInfo *resultInfo) {
                NSLog(@"%ld---网络请求接口完成---%ld", i, i);
                /// 离开组
                dispatch_group_leave(group);
                /// 信号量+1，向下运行发送信号
                dispatch_semaphore_signal(semaphore);
            } failure:^(NSError *error) {
                NSLog(@"%@", error.localizedDescription);
                /// 离开组
                dispatch_group_leave(group);
                /// 信号量+1，向下运行发送信号
                dispatch_semaphore_signal(semaphore);
            }];
            /// 等待信号，网络请求通过
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        });
    }
    
    /// 所有任务都完成
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"----网络请求全部完成----");
    });
    
    NSThread *thread = [[NSThread alloc] init]; 
    
    /**
     GCD
     同步执行 sync
     异步执行 async
     
     【同步执行】：同步添加任务到指定的队列中，在添加的任务执行结束之前，会一直等待，直到队列里面的任务完成之后再继续执行
     【异步执行】：异步添加任务到指定的队列中，他不会做任何等待，可以继续执行任务，可以在新的线程中执行任务，具备开启新线程的能力
     
     performSeletor 一定要有runloop才能执行的
     dispatch_async(global_queue, ^{
     };
     GCD底层是没有runloop的
     
     队列
     串行队列
     并发队列
     
     死锁：队列引起的循环等待
     
     
     共同点：复合FIFO 先进先出
     区别：执行顺序不同，以及开启线程数不同
     
     串行队列：每次只有一个任务被执行，让任务一个接着一个执行（只开启一条线程，一个任务执行完毕后，再执行下一个任务）
     并发队列：可以让任务并发(同时执行)（可以开启多个线程，并且同时执行任务） 并发只能在异步函数下才能有效 async
     
     DISPATCH_QUEUE_SERIAL 串行队列/主队列
     DISPATCH_QUEUE_CONCURRENT 并发队列/全局并发队列
     
     // 串行队列的创建方法
     dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);
     // 并发队列的创建方法
     dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
     
     
     同步执行+并发队列
     异步执行+并发队列
     同步执行+串行队列
     异步执行+串行队列
     同步执行+主队列
     异步执行+主队列
     
     区别             并发队列                串行队列                主队列
     同步sync     没有开启线程，串行执行     没有开启线程，串行执行     其他线程调用没有开启线程，串行执行、主线程调用 【死锁】互相卡住不执行
     异步async    开启新线程，并发执行       有开启一条线程，串行执行   没有开启线程
     
     异步执行只有遇到主队列才不会开启线程，其他情况都会开启新线程
     
     同步+主队列: 死锁
     主线程中执行sync方法，相当于把sync任务放到了主线程的队列中
     同步执行会等待当前队列中的任务执行完毕，才会接着执行
     当把任务追加到主队列中，任务就会等待主线程处理完sync任务，而sync任务需要等待任务执行完毕，才能接着执行
     

     信号量
     Dispatch Semaphore 持有计数的信号
     使用Dispatch Semaphore中，使用计数来完成这个功能
     计数为0时等待，不可通过
     计数为1s或大于1时候，技数减1且不等待，可通过
     
     dispatch_semaphore_create:创建一个Semaphore提供三个函数
     dispatch_semaphore_signal:发送一个信号，让信号总量加1
     dispatch_semaphore_wait:可以使总信号量减1，当信号量为0就会一直等待(阻塞所在线程)，否则可以正常执行
     
     Dispatch Semaphore 在实际开发中的作用：
     1.保持线程同步，将异步执行任务转换为同步执行任务
     2.保证线程安全，为线程加锁
     
     */
}

- (void)test3
{
    NSLog(@"currentThread --- %@", [NSThread currentThread]);
    NSLog(@"syncMain---开始");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    /**
     XCode9之后会崩溃
     Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
     */
    dispatch_sync(queue, ^{
        /// 追加任务1
        for (NSInteger i = 0; i < 2; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        /// 追加任务2
        for (NSInteger i = 0; i < 2; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2---%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"syncMain---结束");
}

- (void)test4
{
    /**
     dispatch_barrier 有两种方法
     
     dispatch_barrier_sync 在队列中充当一个栅拦的作用，凡是在他之后 进入队列的任务，总会在dispatch_barrier_sync之前的所有任务执行完毕之后才能执行
     dispatch_barrier_sync 会在主线程执行队列中的任务的，所有会有阻塞
     dispatch_barrier_async 会开辟新线程，不会阻塞当前线程
     
     dispatch_barrier和串行队列是无意义的
     
     barrier的目的是在某种情况下，同一个队列中一些并发任务必须在另一些并发任务之后执行，
     所以需要一个类似于拦截的功能，同一个队列中一些并发任务必须在另一些并发任务之后执行，所以需要类似于拦截的功能，迫使后执行
     本来串行队列职工的所有任务本身按照顺序执行的，所以没必要使用拦截的功能

     */

    /// 测试异步执行
    [self testBarrierAsyncWithCurrentQueue];
    
    /// 测试同步执行
//    [self testBarrierSyncWithCurrentQueue];
}

/// 异步
- (void)testBarrierAsyncWithCurrentQueue
{
    /// 一定要是并行的，串行没意义 DISPATCH_QUEUE_SERIAL串行
    dispatch_queue_t queue = dispatch_queue_create("barrier", DISPATCH_QUEUE_CONCURRENT);

    for (NSInteger i = 0; i < 10; i ++) {
        dispatch_async(queue, ^{
            NSLog(@"index = %ld", i);
        });
    }
    
    for (NSInteger j = 0; j < 100; j ++) {
        dispatch_barrier_async(queue, ^{
            if (j == 100 - 1) {
                NSLog(@"Barrier - Finished");
                NSLog(@"current thread is %@", [NSThread currentThread]);
            }
        });
    }
    
    NSLog(@"Running on Main Thread");
    
    for (NSInteger index = 10; index < 20; index ++) {
        dispatch_async(queue, ^{
            NSLog(@"index = %ld", index);
        });
    }
    
    /*
      index = 0
      index = 1
      index = 2
      index = 3
      index = 4
      index = 6
      index = 5
      index = 7
      index = 8
      Running on Main Thread
      index = 9
      Barrier - Finished
      current thread is <NSThread: 0x600003488000>{number = 3, name = (null)}
      index = 10
      index = 11
      index = 12
      index = 13
      index = 14
      index = 15
      index = 16
      index = 17
      index = 18
      index = 19
     */
    
}

/// 同步
- (void)testBarrierSyncWithCurrentQueue
{
    /// 一定要是并行的，串行没意义 DISPATCH_QUEUE_SERIAL串行
    dispatch_queue_t queue = dispatch_queue_create("barrier", DISPATCH_QUEUE_CONCURRENT);
    
    for (NSInteger i = 0; i < 10; i ++) {
        dispatch_async(queue, ^{
            NSLog(@"index = %ld", i);
        });
    }
    
    for (NSInteger j = 0; j < 100; j ++) {
        dispatch_barrier_sync(queue, ^{
            if (j == 100 - 1) {
                NSLog(@"Barrier - Finished");
                NSLog(@"current thread is %@", [NSThread currentThread]);
            }
        });
    }
    
    NSLog(@"Running on Main Thread");
    
    for (NSInteger index = 10; index < 20; index ++) {
        dispatch_async(queue, ^{
            NSLog(@"index = %ld", index);
        });
    }
    
    /**
      index = 0
      index = 1
      index = 2
      index = 3
      index = 5
      index = 6
      index = 7
      index = 8
      index = 9
      Barrier - Finished
      current thread is <NSThread: 0x600002b08d80>{number = 1, name = main}
      Running on Main Thread
      index = 10
      index = 11
      index = 12
      index = 13
      index = 14
      index = 15
      index = 16
      index = 17
      index = 18
      index = 19
     */
    
}

/**
 
 NSMutableDictionary 不是线程安全的，任何一个线程在往字典里写入数据的时候是不允许有其他线程访问的，不管是读或者写都是不可以的，所以现在的任何就是，我们需要使用GCD来实现一个线程安全的NSMutableDictionary
 
 串行队列 + dispatch_async
 并发队列 + dispatch_async
 
 变量，他的声明周期只是在block里面
 
 */


@end
